home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-05-14 | 53.4 KB | 1,641 lines | [TEXT/CWIE] |
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Apple Macintosh Developer Technical Support
-
- SoundUnit
-
- SoundUnit.c - C Source
-
- Versions:
- 1.03 January, 1990
- 1.04 Sept, 1990
- 1.2 August, 1994 translated to C
-
- Components:
- SoundApp.c January, 1990 MPW C source code
- SoundUnit.c January, 1990 MPW C source code
- SoundUnit.h January, 1990 MPW C source code
- SoundApp.r January, 1990 MPW Rez source code
- SoundAppSnds.r January, 1990 MPW Rez source code
- SoundApp.make January, 1990 MPW build script
-
- Formatting was done with FONT = Courier or Monaco, SIZE = 10, TABS = 4
-
- Version comments
-
- 1.1: This is the "new" SoundUnit which adds some new features.
- • Some knowledge of the new Sound Manager is present
- in areas that were work-arounds for old Sound Manager bugs
- • Conversion to MPW 3.2 was established (with some amount of pain)
- • Added the new Sound Manager error strings
- • Checking of the sound header for supported encode values
- • The amp value is ignored (never used) in a freqDurationCmd
- • Added functions to test sound hardware/software features
- such as stereo, MACE, Sound Input
-
-
- Formatting was done with FONT = Courier, SIZE = 10, TABS = 4
-
-
- SoundApp.c is a sample application source file for demonstrating the
- Sound Manager. This portion of the source code handles the Sound Manager
- part of the application. This source can be used by others.
-
- Jim Reekes
- Sunday, August 7, 1994 7:06:41 PM
- */
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- //includes
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- #include <Errors.h>
- #include <FixMath.h>
- #include <fp.h>
- #include <GestaltEqu.h>
- #include <LowMem.h>
- #include <Memory.h>
- #include <MixedMode.h>
- #include <Resources.h>
-
- #include <limits.h>
- #include <stddef.h>
-
- #include "Sound.h"
- #include "SoundInput.h"
- #include "SoundUnit.h"
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // private constants
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- enum {
- kMaxChannels = 4, //maximum number of supported channels
-
- kSoundComplete = 0x1234 //flag for callBackCmd
- };
-
- /*
- These are used as flags in the sound channel to determine the state
- of that channel.
- */
- enum {
- kChanFreeState = 0, //channel is not in use
- kChanCompleteState //channel has completed
- };
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // macros
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- #if USESROUTINEDESCRIPTORS
- #define CreateRoutineDescriptor(info, proc) \
- RoutineDescriptor g##proc##RD = BUILD_ROUTINE_DESCRIPTOR(info, proc)
-
- #define GetRoutineAddress(proc) (&g##proc##RD)
-
- #else
- #define GetRoutineAddress(proc) proc
- #endif
-
- // this belongs in LowMem.h
- extern unsigned short LMGetSoundActive( void )
- TWOWORDINLINE( 0x1038, 0x027E ); /* MOVE.B $027E, D0 */
-
- // For Power Macs, there is no Sound Driver and therefore SoundActive in
- // low memory should not be a problem. This is sometimes set by third parties
- // that are writting directly to the hardware, and also set by the old
- // sound driver when it is active. When this is happening, the Sound Manager
- // cannot work. So we check the low memory global to be less than 0. But
- // for Power Mac builds, we just return 0.
-
- #if USESROUTINEDESCRIPTORS
- #define SoundDriverActive() false
- #else
- #define SoundDriverActive() (LMGetSoundActive() < 0)
- #endif
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // private types
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- /*
- This is an element of a globals array that is used to keep track of
- sound channels created by the sound unit. It contains all the useful
- information associated with the channel. I keep the 'snd ' resource
- handle associated to this channel too. This allows me to dispose of
- the data once the channel has completed its duties.
- */
-
- struct ChanInfo {
- SndChannelPtr chan;
- SndListHandle dataHandle;
- short chanState;
- short chanType;
- };
- typedef struct ChanInfo ChanInfo;
- typedef ChanInfo *ChanInfoPtr;
-
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // private prototypes
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- void FreeChan(ChanInfoPtr info);
- OSErr ChanAvailable(ChanInfoPtr info);
- Boolean CompatibleChan(ChanInfoPtr info);
- OSErr InstallSampleSnd(ChanInfoPtr info, SndListHandle sndHandle);
- OSErr NewWaveChan(ChanInfoPtr info, short init);
-
- pascal void DoCallBack(SndChannelPtr chan, SndCommand *theCmd);
- Boolean IsMyChan(SndChannelPtr chan);
- OSErr SndDataAvailable(SndListHandle sndHandle);
- ModRef GetSynthInfo(SndListHandle sndHandle);
- Boolean SupportedSH(SoundHeaderPtr sndPtr);
- OSErr ReleaseSynch(SndChannelPtr chan);
- OSErr Synch1Chan(SndChannelPtr chan, short count);
- OSErr SynchChans(SndChannelPtr chan1, SndChannelPtr chan2, SndChannelPtr chan3, SndChannelPtr chan4);
-
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- //globals (The “g” prefix is used to emphasize that a variable is global.)
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- /*
- This is the global array of sound channel information.
- */
-
- ChanInfoPtr gChanInfo;
-
- /*
- gSoundMgrVersion is used to determine if the application is running
- with the old Sound Manager. This was shipped prior to System 6.0.6.
- This flag is setup in the InitSoundUnit routine and used by the rest of this
- source file. There are workarounds to problems based on this condition.
- */
- short gSoundMgrVersion;
-
- // allocate the RoutineDescriptors for Power Mac toolbox calls
- #if USESROUTINEDESCRIPTORS
- CreateRoutineDescriptor(uppSndCallBackProcInfo, DoCallBack);
- #endif
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is a dummy routine to allow the application to unload this segment.
- */
-
- #pragma segment SoundUnit
- pascal void _SoundUnit(void)
- {
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Create storage for each of the four channels (4 * 1064 bytes) and
- initialize them. If any of the channels cannot be allocated, return an
- error. If the user only wants one channel, then we could just allocate
- one instead of four. These channels are used at interrupt time.
-
- VERSION 1.1: Added the new Sound Manager flag, gNewSndMgr. First I have to
- test if the _SoundDispatch trap is available, since SndManagerVersion is
- a selector for _SoundDispatch. This this trap isn't available, then calling
- SndManagerVersion would result in an unimplemented instruction. If an error
- is returned, it is the old Sound Manager. If it is zero, then the call
- is available (via-MIDI Mgr?) but it isn't the new Sound Manager. Greater
- than zero means it is the new Sound Manager.
-
- VERSION 1.2: Only supports Sound Manager 2.0 or later. I would personally
- require version 3.0 or later in my own products.
- */
-
- #pragma segment Initialize
- pascal OSErr InitSoundUnit(void)
- {
- OSErr theErr;
-
- // check if the supported Sound Manager is present, this is the one
- // that has Sound Input and supports the _SoundDispatch trap
-
- theErr = noErr;
- gSoundMgrVersion = GetSoundMgrVersion();
- if (gSoundMgrVersion > 1)
- {
- gChanInfo = (ChanInfoPtr)NewPtrClear(sizeof(ChanInfo[kMaxChannels]));
- if (gChanInfo == nil)
- theErr = MemError();
- }
- return(theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- To determine if MACE is available, there are two case. If the new Sound Manager
- is running then MACE may be built in. This is easy enough to test for by calling
- the new trap call. Otherwise I have to test for the presence of the MACE
- snth resources. The old MACE snths could only be present if the user ran the
- MACE Installer Scripts which are available from APDA. This was only supported
- on some versions of System 6.0.x.
-
- VERSION 1.2: Forget about dealing with MACE prior to Sound Manager 2, it was
- just a hack and didn't work on all machines.
- */
-
- #pragma segment Main
- pascal Boolean HasMACE(void)
- {
- NumVersion version;
- Boolean result;
-
- result = false;
- if (GetSoundMgrVersion() > 1)
- {
- version = MACEVersion();
- result = (version.majorRev > 0); //is the built-in MACE here?
- }
- return (result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is used to determine if Sound Input is available. The Gestalt flag
- gestaltHasSoundInputDevice can be used to determine if an input device is
- available. This flag did not exist prior to System 7. The other related
- flag gestaltBuiltInSoundInput can only be used to determine if the machine
- has built-in sound input hardware, so don't be mislead. In System 6.0.7 you
- would have to use SPBGetIndexdDevice to find the fist one. If this returns
- noErr then Sound Input is available. Also, the icon handle returned by this
- call has to be disposed of by you the caller.
-
- VERSION 1.1: This is the external routine for users to determine if
- sound input is available.
-
- VERSION 1.2: Use the Gestalt method since we know that we're running Sound
- Manager 2 or later which supports the gestaltHasSoundInputDevice flag.
- */
-
- #pragma segment Main
- pascal Boolean HasSoundInput(void)
- {
- long response;
- OSErr theErr;
- Boolean result;
-
- theErr = Gestalt(gestaltSoundAttr, &response);
- if ( (theErr == noErr) && (response & (1<<gestaltHasSoundInputDevice)) )
- result = true;
- else
- result = false;
-
- return (result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- VERSION 1.1: This is the external routine for users to determine if
- sound input is available. Sound Manager 3.0 can always support stereo,
- even on mono hardware.
- */
-
- #pragma segment Main
- pascal Boolean HasStereoSupport(void)
- {
- long response;
- OSErr theErr;
- Boolean result;
-
- theErr = Gestalt(gestaltSoundAttr, &response);
- if ( (theErr == noErr) && (response & (1<<gestaltStereoCapability)) )
- result = true;
- else
- result = false;
- return (result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This will return the version of the sound manager that is currently running.
- A special case is necessary because the first sound manager that reported
- its version didn't have the SndSoundManagerVersion() implemented. So that means
- older versions are really 1.0, and the first oldest version returned by
- SndSoundManagerVersion is 2.0. Anything older than 2.0 has a few problems.
- */
-
- pascal short GetSoundMgrVersion(void)
- {
- NumVersion version;
- long response;
- short result;
- OSErr theErr;
-
- result = 1;
- theErr = Gestalt(gestaltSoundAttr, &response);
- if ( (theErr == noErr) && (response & (1<<gestaltSoundIOMgrPresent)) )
- {
- version = SndSoundManagerVersion();
- result = version.majorRev;
- }
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This routine can be called to determine if the sound has completed. When
- this is true, the data can be disposed of. It is set by the sound
- channel's completion routine. It is placed in the Main segment because
- it is called by the event loop.
- */
-
- #pragma segment Main
- pascal Boolean HasSoundCompleted(void)
- {
- short i;
- Boolean result;
-
- result = true; //assume true, then check for busy channels
- for (i = 0; i < kMaxChannels; i++)
- {
- // if we have a channel and it is not completed, then we're still busy playing
- if ((gChanInfo[i].chan != nil) && (gChanInfo[i].chanState != kChanCompleteState))
- {
- result = false;
- break;
- }
- }
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This routine can be called at any time. It will return true when the
- SoundUnit has an open channel. This can be can considered the same as
- sound being active. As long as the channel is open, no other channels can
- be opened. It is placed in the Main segment because it is called by the
- event loop.
- */
-
- #pragma segment Main
- pascal Boolean HasChannelOpen(void)
- {
- short i;
- Boolean result;
-
- result = false;
- for (i = 0; i < kMaxChannels; i++)
- {
- if (gChanInfo[i].chan != nil)
- {
- result = true;
- break;
- }
- }
-
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Given a channel and timbre(sounds like "tom burr"), this will adjust the
- tone quality of the square wave synthesizer. Changing the tone can only be done
- before playing a square wave. On a Mac with the Apple Sound Chip, this can be
- done in real time while a note is playing. But, since there's no
- supported method for determining if the ASC is available I have to assume
- that it's not. I use the immediate flag to determine if the user wants to
- change the timbre now, or queue the command. If the queue is full, it
- will wait for the command to be accepted.
-
- BUG NOTE: There is a bug in the Sound Manager running on the Mac Plus or
- SE where sending a timbreCmd with a timbre of 255(a legal value)will
- crash. The difference between 254 and 255 isn't audible, so I only allow
- a maximum of 254 in any case.
- */
-
- #pragma segment SoundUnit
- pascal OSErr SetSquareWaveTimbre(SndChannelPtr squareChan, short timbre, Boolean immediate)
- {
- SndCommand theCmd;
- OSErr result;
-
- if (timbre > 254)
- timbre = 254;
-
- theCmd.cmd = timbreCmd;
- theCmd.param1 = timbre;
- theCmd.param2 = 0;
-
- if (immediate)
- result = SndDoImmediate(squareChan, &theCmd);
- else
- result = SndDoCommand(squareChan, &theCmd, kWait);
-
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Given a channel and note information, this will place the note into the
- channel's queue. The note contains the amplitude and note value. It is a
- four byte parameter with the high byte containing the amplitude. I use
- SndDoCommand with the noWait flag set to wait for the channel to except
- the command in the case the queue is currently full.
- */
-
- #pragma segment SoundUnit
- pascal OSErr SendNote(SndChannelPtr chan, short duration, long note)
- {
- SndCommand theCmd;
-
- theCmd.cmd = freqDurationCmd;
- theCmd.param1 = duration;
- theCmd.param2 = note;
-
- return(SndDoCommand(chan, &theCmd, kWait));
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Given a channel, this will place a quietCmd into the channel's queue. I
- use SndDoCommand with the noWait flag set to wait for the channel to
- except the command in the case it is currently full.
-
- BUG NOTE: A sequence of notes and rests will not work unless quietCmds
- are between them. Rests have to be made quiet before they rest, if that
- makes any more sense. A freqDurationCmd will loop, causing the sound in progress
- to continue, until a quietCmd is received.
- */
-
- #pragma segment SoundUnit
- pascal OSErr SendQuiet(SndChannelPtr chan, Boolean immediate)
- {
- SndCommand theCmd;
- OSErr result;
-
- theCmd.cmd = quietCmd;
- theCmd.param1 = 0;
- theCmd.param2 = 0;
-
- if (immediate)
- result = SndDoImmediate(chan, &theCmd);
- else
- result = SndDoCommand(chan, &theCmd, kWait);
-
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Given a channel and duration, this will place the rest into the channel's
- queue. Before sending a rest a quietCmd is needed. Rests don't work
- unless you tell the Sound Manager to be quiet too. I use SndDoCommand
- with the noWait flag set to wait for the channel to except the command in
- the case it is currently full.
- */
-
- #pragma segment SoundUnit
- pascal OSErr SendRest(SndChannelPtr chan, short duration)
- {
- SndCommand theCmd;
- OSErr theErr;
-
- theErr = SendQuiet(chan, kWait);
-
- if (theErr == noErr) {
- theCmd.cmd = restCmd;
- theCmd.param1 = duration;
- theCmd.param2 = 0;
-
- theErr = SndDoCommand(chan, &theCmd, kWait);
- }
-
- return(theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Test if the channel is free, and if not then call SndDisposeChannel.
- This will release the synthesizer(snth resource)code and the
- required hardware. If we didn't do this, no other channels would
- work. I also test myChan for having a snd resource attached to the
- channel. If so, then I mark it as purgeable and reset the data to nil.
-
- BUG NOTE: Calling SndDisposeChannel while or immediately after playing
- a sequence of notes would often hang/crash a non-Apple Sound Chip based Mac.
- Issuing a quietCmd first kept the Sound Manager happy and my Mac from
- crashing.
- */
-
- #pragma segment SoundUnit
- void FreeChan(ChanInfoPtr info)
- {
- OSErr theErr;
-
- if (info->chan != nil) {
- theErr = SendQuiet(info->chan, !kWait); // ignore error
- theErr = SndDisposeChannel(info->chan, !kWait);
- info->chan = nil;
- info->chanState = kChanFreeState;
- }
-
- if (info->dataHandle != nil) {
- HUnlock((Handle)info->dataHandle);
- HPurge((Handle)info->dataHandle);
- info->dataHandle = nil;
- }
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This routine is called by an application that established a sound to be
- played asynchronously. This is in effect, the routine to be used after
- the completion routine has been called. The application should call this
- once HasSoundCompleted() returns true. In the case the application is using
- multiple channels, we will only free a channel once it has been marked as
- kChanCompleteState.
- */
-
- #pragma segment SoundUnit
- pascal void DoSoundComplete(void)
- {
- short i;
-
- for (i = 0; i < kMaxChannels; i++)
- {
- if (gChanInfo[i].chanState != kChanFreeState)
- FreeChan(&gChanInfo[i]);
- }
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is the routine that will force all channels to be released This is used by
- all routines just before opening a new channel to force all channels to be disposed.
- */
-
- #pragma segment SoundUnit
- pascal void FreeAllChans(void)
- {
- short i;
-
- for (i = 0; i < kMaxChannels; i++)
- FreeChan(&gChanInfo[i]);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is the final routine to be called by the application when it is has
- finished using this SoundUnit. This will dispose of all the channels and
- memory used by this SoundUnit.
- */
-
- #pragma segment SoundUnit
- pascal void FreeSoundUnit(void)
- {
- FreeAllChans();
- DisposPtr((Ptr)gChanInfo);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This will be called at interrupt time by the Sound Manager when it
- receives a callBackCmd. I use the second parameter of the command to hold
- my application's A5 reference. I first set up A5 so that I can access my
- globals. I mark the given channel as being complete. This lets the
- application know that the callBackCmd has been processed. The callBackCmd
- can be used for other purposes, and the first parameter of the command
- could be a flag to a more extensive routine. Synchronizing the application
- with the channel is possible with this method.
-
- WARNING: This routine MUST be resident in memory and cannot make a call
- to a non-resident segment. I put this into the Main segment because of
- this.
-
- BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
- 'snd '. A bogus callBackCmd is placed into the queue immediately after
- the bufferCmd used to play the sound. This bogus callBackCmd will cause
- my callBackProc to be called when I wasn't expecting it. I have been
- using the command's second parameter to contain my A5 address. If I'm
- given a bogus callBackCmd, it would be really bad to set A5 address to
- this bogus parameter in the command. I found that the bogus callBackCmd
- contains the handle to the 'snd ' passed in to _SndPlay. I also found
- that param1 contains the handle's state bits(results of HGetState). To
- work with this bug I set my real callBackCmd's param1 to a specific value
- when I installed it into the queue. See the SoundComplete routine. Then
- I test the callBackCmd to make sure I'm dealing with the real one.
-
- VERSION 1.2: No longer using A5 globals. Get the address that we need
- in param2, instead of passing in our application's A5 address.
- */
-
- #pragma segment Main
- pascal void DoCallBack(SndChannelPtr chan, SndCommand *theCmd)
- {
- #pragma unused (chan)
- ChanInfoPtr info;
-
- if (theCmd->param1 == kSoundComplete) // if it's my callBackCmd
- {
- info = (ChanInfoPtr)theCmd->param2;
- info->chanState = kChanCompleteState; // this channel is done
- }
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- I use this to install the callBackCmd into the given channel. I need to
- pass in our A5 along with the command so that the callBack routine can
- access my globals. I also wait until the channel is ready for another
- command in the case of the channel being full. Once the Sound Manager
- calls my call back procedure I will dispose of the channel. So, this is
- the last sound command to be sent to a channel. I pass to the call back
- A5 in the second parameter of the callBackCmd. Refer to Tech Note #208.
-
- VERSION 1.2: No longer using A5 globals. Get the address that we need
- in param2, instead of passing in our application's A5 address.
- */
-
- #pragma segment SoundUnit
- pascal OSErr SoundComplete(SndChannelPtr chan)
- {
- SndCommand theCmd;
- OSErr result;
- short i;
-
- theCmd.cmd = callBackCmd;
- theCmd.param1 = kSoundComplete;
- theCmd.param2 = 0; // initialize value
-
- for (i = 0; i < kMaxChannels; i++)
- {
- if (gChanInfo[i].chan == chan)
- {
- theCmd.param2 = (long)(&gChanInfo[i]);
- break;
- }
- }
- if (theCmd.param2 == 0)
- result = badChannel; // channel wasn't one of ours
- else
- result = SndDoCommand(chan, &theCmd, kWait);
-
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This routine will test the given channel to see if it will really produce
- sound. The Sound Manager in System 6.0x will return noErr even if the
- channel isn't going to work. In the future the Sound Manager will return
- the proper error. Until then, I use this routine to determine this for me.
- There can only be a single channel at any time, unless I have the wave
- table synthesizer open. This will allow four channels. Channels have
- a pointer to the next channel, and if this is not nil I suspect the
- given channel will not work. I test the given channel for being a
- wave type, and if so I need to see if the other channels I've got are
- also wave type. If it doesn't look like the channels is available, I
- return badChannel.
-
- This routine assumes the channel passed in is the first channel allocated.
- Channels are held in a linked list, and the first one to be tested has
- to be the first one allocated.
-
- BUG NOTE: If an application is not using the Sound Manager and instead
- uses the older Sound Driver, any given channel will fail. Or if the other
- application does not release is channels, then my channels will not work.
- The most noticeable offender of this is HyperCard. Friendly applications
- will dispose of their channels at suspend/resume times or ASAP.
-
- VERSION 1.1: Added the new Sound Manager test. The new Sound Manager will
- allow multiple sound channels, and returns proper error codes.
-
- VERSION 1.2: This is not used since we only support Sound Manager 2.0 or later.
- */
-
- #pragma segment SoundUnit
- OSErr ChanAvailable(ChanInfoPtr info)
- {
- OSErr result;
- short i;
-
- if (gSoundMgrVersion >= 3)
- return(noErr);
-
- result = noErr;
-
- if (SoundDriverActive()) // check for sound driver
- return(notEnoughHardwareErr);
-
- if (info->chan->nextChan != nil) // looks bad
- {
- result = badChannel; // prepart to fail
- if (info->chanType == waveTableSynth) // last attempt
- {
- if (IsMyChan(info->chan->nextChan))
- {
- for (i = 0; i < kMaxChannels; i++)
- {
- if (! (CompatibleChan(&gChanInfo[i])))
- break;
- result = noErr; // got lucky
- }
- }
- }
- }
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is a utility routine that works with ChanAvailable. It checks to
- see if the given channel is one of the four channels we opened. If it
- is, IsMyChan returns true. If it isn't, false is returned.
- */
-
- #pragma segment SoundUnit
- Boolean IsMyChan(SndChannelPtr chan)
- {
- short i;
- Boolean result;
-
- result = false;
- for (i = 0; i < kMaxChannels; i++)
- {
- if (gChanInfo[i].chan == chan)
- {
- result = true;
- break;
- }
- }
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is a utility routine that works with ChanAvailable. It returns
- true if the given channel is either a wave type of channel. It also
- returns true if the channel is free. Otherwise, it returns false.
- */
-
- #pragma segment SoundUnit
- Boolean CompatibleChan(ChanInfoPtr info)
- {
- Boolean result;
-
- if ( (info->chanType == waveTableSynth) // wave or..
- || (info->chanState == kChanFreeState) ) // free chan
-
- result = true;
- else
- result = false;
-
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Given a resource handle, this will attempt to load it into memory. If the
- data is not available, then return an error.
- */
-
- #pragma segment SoundUnit
- OSErr SndDataAvailable(SndListHandle sndHandle)
- {
- OSErr result;
-
- result = noErr;
- if (sndHandle != nil) {
- LoadResource((Handle)sndHandle);
- if (*sndHandle == nil)
- result = nilHandleErr; // master pointer is nil
- }
- else
- result = nilHandleErr; // user passed a nil handle
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is used to put the given sound resource into memory and hold it there.
- I also use a MoveHHi to keep the heap from being fragmented. If this
- fails, then I return an error. I dereference the handle and check if the
- master pointer is nil. This would mean the data could not be loaded.
- */
-
- #pragma segment SoundUnit
- pascal OSErr HoldSnd(SndListHandle sndHandle)
- {
- OSErr result;
-
- if (sndHandle != nil) {
- LoadResource((Handle)sndHandle);
- if (*sndHandle == nil)
- result = nilHandleErr; // master pointer is nil
- else {
- result = noErr;
- HLockHi((Handle)sndHandle);
- }
- }
- else
- result = nilHandleErr; // user passed a nil handle
- return(result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This routine will return the 'snth' resource ID specified by the sound.
- I use this to determine if the given sound will work with _SndPlay.
- This routine does not require the data to be in memory when called. It
- also doesn't lock it down while looking for the information.
-
- VERSION 1.1: If no synth information is found in the snd then by default
- it is assumed to be for the squareWaveSynth.
- */
-
- #pragma segment SoundUnit
- ModRef GetSynthInfo(SndListHandle sndHandle)
- {
- SndListPtr soundPtr;
- OSErr theErr;
- ModRef currSynth;
-
- currSynth.modNumber = kNoSynth; //initialize to no synth, and no init
- currSynth.modInit = 0;
- theErr = SndDataAvailable(sndHandle);
- if (theErr == noErr)
- {
- soundPtr = (*sndHandle);
- if (soundPtr->format == firstSoundFormat)
- {
- if (soundPtr->numModifiers != 0)
- currSynth = soundPtr->modifierPart[0];
- else
- currSynth.modNumber = squareWaveSynth;
- }
- else //snd is a format 2 for HyperCard
- currSynth.modNumber = sampledSynth;
- }
- return(currSynth);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This routine will cruise through the given snd resource It will locate
- the sound data, if any, and return its type and offset into the resource.
- I prefer to return an offset instead of a pointer because I don't want
- to have the data locked in memory. If I return an offset, the caller
- can decided when and if it wants the resource locked down to access the
- sound data. The first step in finding this data is to determine if I'm
- looking at a format 1 or 2 type snd. A type 2 is easy, but a type 1 will
- require me to find the number of snths specified and then to skip over
- each one including the init option. Once this is done, I have a pointer
- to the number of commands in the snd. When I've found the first one, I
- examine it to find out if it is a sound data command. Being it's a sound
- resource, the command will also have its dataPointerFlag set. Once I've
- found a command I'm looking for I return its type and offset, then get out
- of the do-while block. Otherwise I go on to the next command. All of this
- makes it possible to get the sound data for use as an instrument sound.
- Typically this will be a sampled sound.
- */
-
- #pragma segment SoundUnit
- pascal long GetSndDataOffset(SndListHandle sndHandle, short *dataType, short *waveLength)
- {
- Ptr cruisePtr;
- long sndDataOffset;
- short synths;
- short howManyCmds;
-
- sndDataOffset = 0; // initialize to defaults
- *dataType = kNoSynth;
- *waveLength = 0;
- if (sndHandle == nil)
- return (sndDataOffset); // return no data
-
- if (*sndHandle != nil) {
- if ((**sndHandle).format == firstSoundFormat) {
- synths = (**sndHandle).numModifiers;
- cruisePtr = (Ptr)&(**sndHandle).modifierPart;
- cruisePtr += (sizeof(ModRef) * synths);
- }
- else
- cruisePtr = (Ptr)&((**(Snd2ListHandle)sndHandle).numCommands);
- howManyCmds = *(short *)cruisePtr; // pointing at number of cmds
- cruisePtr += sizeof(howManyCmds);
-
- // cruisePtr is now at the first sound command
- // cruise all commands and find a soundCmd or bufferCmd
- do {
- switch (((SndCmdPtr)cruisePtr)->cmd) {
-
- case soundCmd | dataOffsetFlag:
- case bufferCmd | dataOffsetFlag:
- *dataType = sampledSynth;
- sndDataOffset = ((SndCmdPtr)cruisePtr)->param2;
- howManyCmds = 0; // done, get out of loop
- break;
-
- case waveTableCmd | dataOffsetFlag:
- *dataType = waveTableSynth;
- *waveLength = ((SndCmdPtr)cruisePtr)->param1;
- sndDataOffset = ((SndCmdPtr)cruisePtr)->param2;
- howManyCmds = 0; // done, get out of loop
- break;
-
- default: // catch any other type of cmd
- cruisePtr += sizeof(SndCommand);
- howManyCmds -= 1;
- break;
- }
- } while (howManyCmds >= 1); // done with all the commands
- }
- return(sndDataOffset);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- VERSION 1.1: Check the given sound header as being supported by the
- running Sound Manager. The encode fields of the header are tested.
- The standard encode always works. The MACE compressed sound will only work if
- MACE is present. A MACE sound can also be a stereo sound, which will only
- work on stereo hardware. The expanded sound is for a stereo sound and/or
- 16bit samples and is only supported by Sound Manager 2.0 or later.
- If the sound is a stereo sound, then it requires stereo support.
-
- VERSION 1.2: Support for Sound Manager 3 and beyond. Any compressed
- sound may work if Sound Manager 3.0 or later is present.
- */
-
- #pragma segment SoundUnit
- Boolean SupportedSH(SoundHeaderPtr sndPtr)
- {
- Boolean result;
-
- result = false;
- switch (sndPtr->encode)
- {
-
- case stdSH:
- result = true;
- break;
-
- case cmpSH:
- if (gSoundMgrVersion < 3)
- {
- //Sound Manager 2.0 will only support MACE compressed sound
- //and only stereo sound on stereo machines.
-
- if ( (((CmpSoundHeaderPtr)sndPtr)->snthID == MACE3snthID)
- || (((CmpSoundHeaderPtr)sndPtr)->snthID == MACE6snthID) )
- {
- if (((CmpSoundHeaderPtr)sndPtr)->numChannels == 1)
- result = true;
- else
- result = HasStereoSupport();
- }
- }
- else
- result = true; //Sound Manager 3 and later does it all
- break;
-
- case extSH: // first check for 8 bit sounds
- if (gSoundMgrVersion < 3)
- {
-
- if (((ExtSoundHeaderPtr)sndPtr)->sampleSize == 8)
- {
- if (((ExtSoundHeaderPtr)sndPtr)->numChannels == 1)
- result = true; // it's a mono sound, no problem
- else
- result = HasStereoSupport(); // only if we have stereo
- }
- else
- result = false; // non-8 bit not allowed
- }
- else // must be a 16 bit sound, Sound Manager 3 and later does it all
- result = true;
- break;
-
- }
- return (result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Given a channel and sampled sound resource, this routine will install the
- sound into the channel for use as an instrument. This allows an
- application to send freqDurationCmds to the channel and play a melody. If I sent
- a bufferCmd instead of the soundCmd, the Sound Manager would play the
- sampled sound. This is basically what _SndPlay would do with a format 2
- snd. I insure that I am using only the proper buffer format having the
- standard encode option. If I were to support compressed sounds, I would
- have to call the MACE synthesizers to expand the buffer before I can use
- it as an instrument. If I don't get a sampled sound of standard encoding
- I'll return a bad format error. I use _SndDoImmediate to get the sound
- installed because I don't want this command to be queued.
-
- VERSION 1.1: Check for a supported sound header.
- */
-
- OSErr InstallSampleSnd(ChanInfoPtr info, SndListHandle sndHandle)
- {
- SndCommand theCmd;
- SoundHeaderPtr dataPtr;
- long dataOffset;
- short sndDataType;
- short ignore;
- OSErr theErr;
-
- theErr = HoldSnd(sndHandle);
- if (theErr == noErr) {
- dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
- if (sndDataType == sampledSynth) {
- dataPtr = (SoundHeaderPtr)((long)(*sndHandle) + dataOffset);
- if (stdSH == dataPtr->encode) {
- theCmd.cmd = soundCmd;
- theCmd.param1 = 0;
- theCmd.param2 = (long)dataPtr;
- info->dataHandle = sndHandle;
- theErr = SndDoImmediate(info->chan, &theCmd);
- } else
- theErr = badFormat; //return a bad format error
- } else
- theErr = badFormat; //return a bad format error
- if (theErr != noErr) {
- HUnlock((Handle)sndHandle); //and free up the resource
- HPurge((Handle)sndHandle);
- }
- }
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This routine will create a sampled sound channel using the INIT option
- given. Typically this will be 0. In any case with System 6.0x this
- option is ignored by the sampled sound synthesizer. The given sound
- resource will be installed into the channel for use as an instrument.
-
- WARNING: If the application does not want an instrument sound, then the
- sndInstrument handle MUST be passed in as nil.
-
- BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
- a Memory Manager error when allocating its internal buffer. There is a
- call to NewPtr(1316)and if a nil is returned, the Sound Manager will
- write randomly to low memory. This can occur when calling _SysBeep under
- low memory conditions. Also, this pointer is allocated into the
- application's heap instead of the system's.
-
- VERSION 1.2: We no longer need to call ChanAvailable since we're only
- supporting Sound Manager 2.0 or later.
- */
-
- #pragma segment SoundUnit
- pascal OSErr GetSampleChan(SndChannelPtr *sampleChan, long init, SndListHandle sndInstrument)
- {
- OSErr theErr;
-
- FreeAllChans();
- theErr = SndNewChannel(&(gChanInfo[0].chan), sampledSynth,
- init, GetRoutineAddress(DoCallBack));
- if (theErr == noErr) {
- gChanInfo[0].chanType = sampledSynth;
- theErr = InstallSampleSnd(&gChanInfo[0], sndInstrument);
- }
- if (theErr != noErr)
- FreeAllChans();
- *sampleChan = gChanInfo[0].chan;
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Only supported by Sound Manager 2.0 and later
-
- */
-
- #pragma segment SoundUnit
- pascal OSErr Get4SampleInstruments(SndChannelPtr *sampleChan1, SndChannelPtr *sampleChan2,
- SndChannelPtr *sampleChan3, SndChannelPtr *sampleChan4,
- SndListHandle sndInstrument1, SndListHandle sndInstrument2,
- SndListHandle sndInstrument3, SndListHandle sndInstrument4)
- {
- OSErr theErr;
-
- FreeAllChans();
-
- theErr = SndNewChannel(&(gChanInfo[0].chan), sampledSynth,
- kInitNone, GetRoutineAddress(DoCallBack));
- if (theErr == noErr) {
- gChanInfo[0].chanType = sampledSynth;
- theErr = InstallSampleSnd(&gChanInfo[0], sndInstrument1);
- if (theErr == noErr) {
- theErr = SndNewChannel(&(gChanInfo[1].chan), sampledSynth,
- kInitNone, GetRoutineAddress(DoCallBack));
- if (theErr == noErr) {
- gChanInfo[1].chanType = sampledSynth;
- theErr = InstallSampleSnd(&gChanInfo[1], sndInstrument2);
- if (theErr == noErr) {
- theErr = SndNewChannel(&(gChanInfo[2].chan), sampledSynth,
- kInitNone, GetRoutineAddress(DoCallBack));
- if (theErr == noErr) {
- gChanInfo[2].chanType = sampledSynth;
- theErr = InstallSampleSnd(&gChanInfo[2], sndInstrument3);
- if (theErr == noErr) {
- theErr = SndNewChannel(&(gChanInfo[3].chan), sampledSynth,
- kInitNone, GetRoutineAddress(DoCallBack));
- if (theErr == noErr) {
- gChanInfo[3].chanType = sampledSynth;
- theErr = InstallSampleSnd(&gChanInfo[3], sndInstrument4);
- }
- }
- }
- }
- }
- }
- }
-
- if (theErr == noErr)
- {
- *sampleChan1 = gChanInfo[0].chan;
- *sampleChan2 = gChanInfo[1].chan;
- *sampleChan3 = gChanInfo[2].chan;
- *sampleChan4 = gChanInfo[3].chan;
- }
- else
- FreeAllChans();
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Given a channel and pointer to a wave table, this will install the wave
- for use as an instrument into the channel. If I find the application
- giving me a nil pointer, I'll return an error. I use _SndDoImmediate
- to get the sound installed because I don't want this to be queued.
- */
-
- #pragma segment SoundUnit
- pascal OSErr InstallWave(SndChannelPtr waveChan, Ptr aWavePtr, short waveLength)
- {
- SndCommand theCmd;
- OSErr result;
-
- if (aWavePtr != nil) {
- theCmd.cmd = waveTableCmd;
- theCmd.param1 = waveLength;
- theCmd.param2 = (long)aWavePtr;
- result = SndDoImmediate(waveChan, &theCmd);
- }
- else
- result = memPCErr; // Pointer Check failed
- return (result);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- NewWaveChan creates a new wave table channel and sets myChan to point
- to it. If any error occurs, the error code is returned as a function
- result.
- */
-
- #pragma segment SoundUnit
- OSErr NewWaveChan(ChanInfoPtr info, short init)
- {
- OSErr theErr;
-
- theErr = SndNewChannel(&(info->chan), waveTableSynth,
- init, GetRoutineAddress(DoCallBack));
- if (theErr == noErr)
- info->chanType = waveTableSynth;
-
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This will return four wave table channels with their waves installed.
- This must be done before calling ChanAvailable. Otherwise that test will
- fail. If I cannot obtain all four wave channels I will dispose of the
- ones I did get before returning the error. This routine expects to find
- four wave table pointers, or it will fail.
-
- Versions 1.2: After calling NewWaveChan, we do not need to call ChanAvailable
- since we know that Sound Manager 2 or later will not do the wrong thing.
- */
-
- #pragma segment SoundUnit
- pascal OSErr GetWaveChans(SndChannelPtr *waveChan1, SndChannelPtr *waveChan2,
- SndChannelPtr *waveChan3, SndChannelPtr *waveChan4)
- {
- OSErr theErr;
-
- FreeAllChans();
-
- theErr = NewWaveChan(&gChanInfo[0], waveInitChannel0);
- if (theErr == noErr)
- {
- theErr = NewWaveChan(&gChanInfo[1], waveInitChannel1);
- if (theErr == noErr)
- {
- theErr = NewWaveChan(&gChanInfo[2], waveInitChannel2);
- if (theErr == noErr)
- theErr = NewWaveChan(&gChanInfo[3], waveInitChannel3);
- }
- }
-
- if (theErr != noErr)
- FreeAllChans(); // we didn't make it
- else {
- *waveChan1 = gChanInfo[0].chan;
- *waveChan2 = gChanInfo[1].chan;
- *waveChan3 = gChanInfo[2].chan;
- *waveChan4 = gChanInfo[3].chan;
- }
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This will create a channel for the square wave synthesizer. There
- are no INIT options used by this synthesizer, but I will set the timbre
- to adjust the tone quality.
-
- VERSION 1.2: We no longer need to call ChanAvailable since we're only
- supporting Sound Manager 2.0 or later.
- */
-
- #pragma segment Sound
- pascal OSErr GetSquareWaveChan(SndChannelPtr *squareChan, short timbre)
- {
- OSErr theErr;
-
- FreeAllChans();
- theErr = SndNewChannel(&(gChanInfo[0].chan), squareWaveSynth,
- kInitNone, GetRoutineAddress(DoCallBack));
- if (theErr == noErr) {
- gChanInfo[0].chanType = squareWaveSynth;
- theErr = SetSquareWaveTimbre(gChanInfo[0].chan, timbre, !kWait);
- }
- if (theErr != noErr)
- FreeAllChans();
- *squareChan = gChanInfo[0].chan;
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is the routine to create a channel that isn't associated with any
- synthesizer. Why? Because if you wanted to use _SndPlay asynchronously
- you need to get such a channel.
-
- BUG NOTE: Do not use a channel already associated to a snth with
- _SndPlay. This causes the Sound Manager to install a second copy of the
- same snth.
-
- VERSION 1.2: We no longer need to call ChanAvailable since we're only
- supporting Sound Manager 2.0 or later.
- */
-
- #pragma segment SoundUnit
- pascal OSErr GetNoSynthChan(SndChannelPtr *chan)
- {
- OSErr theErr;
-
- FreeAllChans();
- theErr = SndNewChannel(&(gChanInfo[0].chan), kNoSynth,
- kInitNone, GetRoutineAddress(DoCallBack));
- if (theErr == noErr)
- gChanInfo[0].chanType = kNoSynth;
- if (theErr != noErr)
- FreeAllChans();
- *chan = gChanInfo[0].chan;
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This routine will use the given channel and snd resource with _SndPlay.
- This is used to play a sound, which is a series of sound commands commonly
- referred to as a sequence. First thing I do is make sure the song fits in
- memory. _SndPlay will lock this resource in memory and then pump the snd
- for all of its worth. I am calling it asynchronously, and if I was using
- a snd that contained sound data I wouldn't mark the snd as being
- purgeable. But in this case, _SndPlay will be done with the snd as soon
- as it returns because it copied all of the commands into the channel.
- (There's no data associated with a sequence, just commands.) _SndPlay
- will not return until it has done so. After _SndPlay I need to work
- around a bug in the freqDurationCmd. The last thing to do is to send a
- callBackCmd to signal me that the channel has completed. If any Sound
- Manager errors are encountered, I return them to the application. If the
- application passed me a nil snd handle, I'll return an error.
-
- WARNING: Make sure you are using a snd that only has note type commands
- in it and not something such as a bufferCmd.
-
- BUG NOTE: There is problem when the final sound command is a freqDurationCmd.
- The note will continue to sound, looping forever, until a quietCmd is sent
- or the channel is disposed of. To prevent unwanted looping, I send a
- quietCmd after all notes. Also read a related bug note when disposing of
- channels in the routine FreeChan().
- */
-
- #pragma segment SoundUnit
- pascal OSErr PlaySong(SndChannelPtr chan, SndListHandle sndSong)
- {
- OSErr theErr;
-
- theErr = SndDataAvailable(sndSong); // get the data loaded
- if (theErr == noErr) {
- theErr = SndPlay(chan, sndSong, kSMAsynch); // pump the sound
- HUnlock((Handle)sndSong);
- HPurge((Handle)sndSong);
- if (theErr == noErr) {
- theErr = SendQuiet(chan, kWait); // work around bug
- if (theErr == noErr)
- theErr = SoundComplete(chan);
- }
- else
- theErr = nilHandleErr; // snd data was not available
- }
- if (theErr != noErr)
- FreeAllChans();
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is used to send a syncCmd to a channel and causes the other channels
- that are being held by a synchCmd to be released. Of course, this assumes
- the application has already called SynchChans. _SndDoImmediate is used
- to get the command directly to the synthesizer bypassing the queue.
-
- BUG NOTE: I've found that immediately clearing the channels and starting
- new ones may cause the channels to startup playing out of synch? This
- happens while disposing the wave channels and starting them immediately.
- */
-
- #pragma segment SoundUnit
- OSErr ReleaseSynch(SndChannelPtr chan)
- {
- SndCommand theCmd;
-
- theCmd.cmd = syncCmd;
- theCmd.param1 = 1;
- theCmd.param2 = kSyncID;
- return (SndDoImmediate(chan, &theCmd));
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is a utility routine for SynchChans. It sends a syncCmd command
- to the channel specified by chan using the count parameter specified
- by count. Synch1Chan returns whatever error that SndDoImmediate returns.
- */
-
- #pragma segment SoundUnit
- OSErr Synch1Chan(SndChannelPtr chan, short count)
- {
- SndCommand theCmd;
-
- theCmd.cmd = syncCmd;
- theCmd.param1 = count;
- theCmd.param2 = kSyncID;
- return (SndDoImmediate(chan, &theCmd));
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- This is used to synchronize four wave table channels. By first sending
- the synchCmd, I can send a sequence of other commands to the channel and
- not have the channel attempt to start processing any of them. That is until
- another synchCmd is sent causing all of the previous synchCmd's counter
- to be decremented. After getting all the channels in synch and sending
- the sequence of further commands, then use the ReleaseSynch routine to
- start all of the channels processing their respective queues.
- _SndDoImmediate is used to get the command directly to the synthesizer
- bypassing the queue.
- */
-
- #pragma segment SoundUnit
- OSErr SynchChans(SndChannelPtr chan1, SndChannelPtr chan2,
- SndChannelPtr chan3, SndChannelPtr chan4)
- {
- OSErr theErr;
-
- theErr = Synch1Chan(chan4, 5);
- if (theErr == noErr) {
- theErr = Synch1Chan(chan3, 4);
- if (theErr == noErr) {
- theErr = Synch1Chan(chan2, 3);
- if (theErr == noErr)
- theErr = Synch1Chan(chan1, 2);
- }
- }
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- In order to synchronize channels, the synchCmd is needed. Once all of the
- song has been sent into each channel, a final synchCmd is issued to
- release them. Don't send more commands into a channel that it can hold at
- one time while the channel is in synch mode.
- */
-
- #pragma segment SoundUnit
- pascal OSErr Play4ChanSongs(SndChannelPtr chan1, SndChannelPtr chan2,
- SndChannelPtr chan3, SndChannelPtr chan4,
- SndListHandle song1, SndListHandle song2,
- SndListHandle song3, SndListHandle song4)
- {
- OSErr theErr;
-
- theErr = SynchChans(chan1, chan2, chan3, chan4);
- if (theErr == noErr) {
- theErr = PlaySong(chan1, song1);
- if (theErr == noErr) {
- theErr = PlaySong(chan2, song2);
- if (theErr == noErr) {
- theErr = PlaySong(chan3, song3);
- if (theErr == noErr) {
- theErr = PlaySong(chan4, song4);
- if (theErr == noErr)
- theErr = ReleaseSynch(chan1);
- }
- }
- }
- }
-
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- WARNING: IT IS RECOMMENDED THAT YOU DO NOT USE THIS CODE. I've provided
- this routine because people have asked me how HyperCard performs its PLAY
- command and why their HyperCard sounds do not sound right using the
- _SndPlay routine. The correct answer is that _SndPlay plays the sound
- correctly. HyperCard is attempting to change the frequency by adjusting
- the sample rate. This is NOT the correct approach. Define the sound
- buffer as it should be played. _SndPlay plays the sound as it is defined.
- If the result from _SndPlay is not what you want, then it is the sample
- that is incorrect and should be edited. The sample rate is the rate at
- which the sound was recorded. If you didn't record it, then how do you
- know what's the correct rate? Set the baseFrequency to the note that was
- recorded. If you recorded middle C at 22k, then the rate is 22k and the
- baseFrequency is middle C. HyperCard is incorrect in using the sample rate as
- the frequency of the sound. Furthermore, using this technique of
- calculating a new sample rate can introduce errors. The resulting sample
- rate will not be the proper pitch. Also, the sample rate for high pitches
- will be very inaccurate and impossible for the Mac to reproduce. Such a
- problem can happen if the given sample rate was 22k and is to be played
- back at three octaves higher. Even 44k samples transposed up a half
- octave will fail. Using the soundCmd and freqDurationCmd will not have this
- problem.
-
- Given a sound resource, this routine will play it in the manner that
- HyperCard does. HyperCard assumes that a sound is to be played at middle
- C when the user does not specify a note value in the PLAY command. I
- don't know why. (What's middle C when I want to hear speech or the sound
- of crickets?) At any rate(pun intended), I get a sampled sound channel.
- I find the sound data offset in the resource, which has to be locked down
- at this time. Once I have the sound data, I get its original sample rate.
- I have to calculate what a new sample rate would be based on its baseFrequency.
- The baseFrequency is the note at which the sound was recorded. I'm not sure
- what this means to crickets, but if this is set to middle C then HyperCard
- doesn't attempt to modify the sample rate. (If you're wondering how the
- math works in this routine, buy a book on music theory. I'm here to
- provide Mac support.) Once I've adjusted the sample rate, I use the
- bufferCmd to play it. Then I restore the sound resource to its original
- state. If I didn't do this it would be possible that the resource was
- still in memory the next time I use it having the adjusted sample rate.
- This would cause me to incorrectly adjust it again. Unlike HyperCard, I
- can do this for both a format 1 and 2.
-
- BUG NOTE: Do not call SANE while the Sound Manager is running. Refer to
- Tech Note #235.
-
- VERSION 1.1: Replaced the previous test of the sound header's encode
- value. The previous version was incorrect. The bufferCmd will automatically
- de-code a MACE compressed sound if MACE is available. Otherwise, the sound
- will not be able to be used. So, a new routine is being used to check for
- supported sound headers. The conflict with the Sound Manager and SANE was
- resolved in the new Sound Manager. The Sound Manager no longers uses extended
- numbers at interrupt level, and instead uses fixed math.
- */
-
- #pragma segment SoundUnit
- pascal OSErr HyperSndPlay(SndListHandle sndHandle)
- {
- SndCommand theCmd;
- OSErr theErr;
- long dataOffset;
- short sndDataType;
- short ignore;
- SoundHeaderPtr dataPtr;
- short thePower;
- double_t newRate;
- Fixed oldRate;
-
- theErr = HoldSnd(sndHandle);
- if (theErr == noErr) {
- theErr = GetSampleChan(&(gChanInfo[0].chan), kInitNone, nil);
- gChanInfo[0].dataHandle = sndHandle; // so FreeAllChans can dispose of data
- if (theErr == noErr) {
- dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
- if (sndDataType == sampledSynth) {
- dataPtr = (SoundHeaderPtr)((long)*sndHandle + dataOffset);
-
- if (SupportedSH(dataPtr)) {
- oldRate = dataPtr->sampleRate; // save original sample rate
- if (dataPtr->baseFrequency != kMiddleC) {
- if (dataPtr->sampleRate > (SHRT_MAX << 16)) // large positive number
- newRate = Fix2X(dataPtr->sampleRate - (SHRT_MAX << 16))
- + (double_t)SHRT_MAX;
- else
- newRate = Fix2X(dataPtr->sampleRate);
- thePower = (kMiddleC) - (dataPtr->baseFrequency);
- dataPtr->sampleRate = X2Fix(newRate *
- pow((double_t)twelfthRootTwo, (double_t)thePower));
- }
- theCmd.cmd = bufferCmd;
- theCmd.param1 = 0;
- theCmd.param2 = (long)dataPtr;
- theErr = SndDoImmediate(gChanInfo[0].chan, &theCmd);
- if (theErr == noErr)
- theErr = SoundComplete(gChanInfo[0].chan);
- dataPtr->sampleRate = oldRate; // restore original sample rate
- } else
- theErr = badFormat; //not SupportedSH
- } else
- theErr = badFormat; //sndDataType not sampledSynth
- }
- }
- if (theErr != noErr)
- FreeAllChans();
- return (theErr);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- /*
- Given a sound resource, this routine will call _SndPlay. The snd must
- be either a format 2 or format 1 that contains snth information.
- Using _SndPlay asynchronously requires us to lock the snd prior to
- calling the trap. The reason being is _SndPlay calls HSetState as soon
- as the trap exits, and restores the handle to whatever is was just before
- the call. This would be bad when using the sound asynchronously. If the
- sound being passed in happens to be a compressed sound created with MACE,
- it will "do the right thing." If MACE isn't around the Sound Manager
- will pretend to play a sound but nothing will be heard.
-
- BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
- a Memory Manager error when allocating its internal buffer. There is a
- call to NewPtr(1316)and if a nil is return, the Sound Manager will write
- randomly to memory. Also, the pointer is allocated into the application's
- heap instead of the system's.
-
- BUG NOTE: _SndPlay when using System 6.0.4 and a sampled sound will send
- a bogus callBackCmd into the channel. This will cause the user's call
- back procedure to be called as soon as the sound has completed. Refer
- to the DoCallBack routine for details.
-
- VERSION 1.1: Add the check for the sound header being supported and
- replaced the check of the snth information. No synth information in the
- snd is valid and would mean to play the snd using the squareWaveSynth.
- */
-
- #pragma segment SoundUnit
- pascal OSErr AsynchSndPlay(SndListHandle sndHandle)
- {
- SoundHeaderPtr dataPtr;
- OSErr theErr;
- long dataOffset;
- short sndDataType;
- short ignore;
-
- theErr = HoldSnd(sndHandle); //hold on to the sound
- if (theErr == noErr) {
- theErr = GetNoSynthChan(&(gChanInfo[0].chan));
- gChanInfo[0].dataHandle = sndHandle; //so FreeAllChans can dispose of data
- if (theErr == noErr) {
- gChanInfo[0].chanType = GetSynthInfo(sndHandle).modNumber;
- dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
- if (sndDataType == sampledSynth) {
- dataPtr = (SoundHeaderPtr)((long)*sndHandle + dataOffset);
- if ( !(SupportedSH(dataPtr)) )
- theErr = badFormat;
- }
- if (theErr == noErr) {
- theErr = SndPlay(gChanInfo[0].chan, sndHandle, kSMAsynch);
- if (theErr == noErr)
- theErr = SoundComplete(gChanInfo[0].chan);
- }
- }
- }
- if (theErr != noErr)
- FreeAllChans();
- return(theErr);
- }
-
-